﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

using System.IO;
using System.Reflection;


namespace UnitVariables
{
    /// <summary>
    /// Class which tracks not only a value but a unit as well. 
    /// UnitVaribs can be added, subtracted, mulitplied and divided
    /// and the units will sort themselves out automagically.
    /// </summary>
    public class UnitVarib
    {
        
        /// <summary>
        /// Private value.
        /// </summary>
        private double val;
        /// <summary>
        /// The value of the variable.
        /// </summary>
        public double Val
        {
            get { return val; }
            set { val = value; }
        }

        /// <summary>
        /// The private unit.
        /// </summary>
        private Unit unit;
        /// <summary>
        /// The unit portion of the variable.
        /// </summary>
        public Unit Unit
        {
            get { return unit; }
            set { unit = value; }
        }

        /// <summary>
        /// Blank constructor initializes to a value of zero and a blank unit.
        /// </summary>
        public UnitVarib()
        {
            Val = 0;
            Unit = new Unit("");
        }

        public UnitVarib(UnitVarib uv)
        {
            Val = uv.Val;
            Unit = uv.Unit;
        }

        /// <summary>
        /// Constructor based on a value and a unit as double and
        /// string respectively.
        /// </summary>
        /// <param name="inVal">Value of the variable as a double.</param>
        /// <param name="inUnit">Value of the unit as a string.</param>
        public UnitVarib(double inVal, string inUnit)
        {
            Val = inVal;
            Unit = new Unit(inUnit);
        }

        /// <summary>
        /// Constructor based on a value and unit as a double and Unit respectively.
        /// </summary>
        /// <param name="inVal">Value of the variable as a double.</param>
        /// <param name="inUnit">Value of the unit as a Unit (class).</param>
        public UnitVarib(double inVal, Unit inUnit)
        {
            Val = inVal;
            Unit = inUnit;
        }

        # region Operators

        /// <summary>
        /// Addition overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator +(UnitVarib v1, UnitVarib v2)
        {
            return new UnitVarib(v1.Val + v2.Val, v1.Unit + v2.Unit);
        }

        /// <summary>
        /// Addition overload.
        /// </summary>
        /// <param name="d">The Double.</param>
        /// <param name="v1">The UnitVarib.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator +(double d, UnitVarib v1)
        {
            return new UnitVarib(v1.Val + d, v1.Unit);
        }

        /// <summary>
        /// Addition overload.
        /// </summary>
        /// <param name="v1">The UnitVarib.</param>
        /// <param name="d">The Double.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator +(UnitVarib v1, double d)
        {
            return new UnitVarib(v1.Val + d, v1.Unit);
        }

        /// <summary>
        /// Subtraction overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator -(UnitVarib v1, UnitVarib v2)
        {
            return new UnitVarib(v1.Val - v2.Val, v1.Unit - v2.Unit);
        }

        /// <summary>
        /// Subtraction overload.
        /// </summary>
        /// <param name="v1">The UnitVarib.</param>
        /// <param name="d">The Double.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator -(UnitVarib v1, double d)
        {
            return new UnitVarib(v1.Val - d, v1.Unit);
        }

        /// <summary>
        /// Subtraction overload.
        /// </summary>
        /// <param name="d">The Double.</param>
        /// <param name="v1">The UnitVarib.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator -(double d, UnitVarib v1)
        {
            return new UnitVarib(v1.Val - d, v1.Unit);
        }

        /// <summary>
        /// Multiplication overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator *(UnitVarib v1, UnitVarib v2)
        {
            return new UnitVarib(v1.Val * v2.Val, v1.Unit * v2.Unit);
        }

        /// <summary>
        /// Multiplication overload for doubles.
        /// </summary>
        /// <param name="v1">UnitVarib</param>
        /// <param name="d">Double.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator *(UnitVarib v1, double d)
        {
            return new UnitVarib(v1.Val * d, v1.Unit);
        }

        /// <summary>
        /// Multiplication overload for doubles.
        /// </summary>
        /// <param name="d">Double.</param>
        /// <param name="v1">UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator *(double d, UnitVarib v1)
        {
            return new UnitVarib(v1.Val * d, v1.Unit);
        }

        /// <summary>
        /// Division overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator /(UnitVarib v1, UnitVarib v2)
        {
            return new UnitVarib(v1.Val / v2.Val, v1.Unit / v2.Unit);
        }


        /// <summary>
        /// Division overload for integers.
        /// </summary>
        /// <param name="v1">UnitVarib</param>
        /// <param name="i">Integer.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator /(UnitVarib v1, int i)
        {
            return new UnitVarib(v1.Val / i, v1.Unit);
        }

        /// <summary>
        /// Division overload for integers.
        /// </summary>
        /// <param name="i">Integer.</param>
        /// <param name="v1">UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator /(int i, UnitVarib v1)
        {
            return new UnitVarib(v1.Val / i, v1.Unit);
        }


        /// <summary>
        /// Division overload for doubles.
        /// </summary>
        /// <param name="v1">UnitVarib</param>
        /// <param name="d">Double.</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator /(UnitVarib v1, double d)
        {
            return new UnitVarib(v1.Val / d, v1.Unit);
        }

        /// <summary>
        /// Division overload for doubles.
        /// </summary>
        /// <param name="d">Double.</param>
        /// <param name="v1">UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator /(double d, UnitVarib v1)
        {
            return new UnitVarib(v1.Val / d, v1.Unit);
        }

        /// <summary>
        /// Power overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib Pow(UnitVarib v, double i)
        {
            return new UnitVarib(Math.Pow(v.Val, i), v.Unit^i);
        }

        /// <summary>
        /// Power overload.
        /// </summary>
        /// <param name="v1">First UnitVarib.</param>
        /// <param name="v2">Second UnitVarib</param>
        /// <returns>Returns the value as a UnitVarib.</returns>
        public static UnitVarib operator ^(UnitVarib v, double i)
        {
            return Pow(v, i);
        }

        # endregion


        /// <summary>
        /// Returns true if the unit (input as a string) is contained
        /// in self. 
        /// </summary>
        /// <param name="str">Input string.</param>
        /// <returns>Boolean true if input is contained in self.</returns>
        public bool Contains(string str)
        {
            return this.Unit.Contains(new Unit(str));
        }

        /// <summary>
        /// Returns true if the Unit is contained in self. 
        /// </summary>
        /// <param name="u1">Input Unit.</param>
        /// <returns>Boolean true if input is contained in self.</returns>
        public bool Contains(Unit u1)
        {
            return this.Unit.Contains(u1);
        }

        /// <summary>
        /// Converts this variable to a specified unit.
        /// </summary>
        /// <param name="convTo">String specifing the desired units.</param>
        /// <returns>Returns a UnitVarib with the converted value in convTo units.</returns>
        public UnitVarib Convert(string convTo)
        {
            UnitVarib retUnit = new UnitVarib();
            // # needs work
            return retUnit;
        }

        public UnitVarib Convert(double multiplier, string convTo)
        {
            UnitVarib retVar = new UnitVarib(this.Val, convTo) * multiplier;
            return retVar;
        }

        /// <summary>
        /// Overload for string to display the values.
        /// </summary>
        /// <returns>String representing the value of the variable.</returns>
        public override string ToString()
        {
            return string.Format("{0} [{1}]", Val, Unit);
        }

        //public string ToNumString()
        //{
        //    return string.Format("{0:n} [{1}]", Val, Unit);
        //}
    }



    /// <summary>
    /// Represents the Unit portion of the variable.
    /// </summary>
    public class Unit
    {
        /// <summary>
        /// List of SingleUnits.
        /// </summary>
        private List<SingleUnit> unitList = new List<SingleUnit>();

        /// <summary>
        /// Constructor based on an input string.
        /// </summary>
        /// <param name="inStr">Input string.</param>
        public Unit(string inStr="")
        {
            string numStr;
            string denomStr;

            if (inStr == "")
                return;

            // Has a numerator and a denominator
            if (inStr.Split('/').Length == 2)
            {
                numStr = inStr.Split('/')[0];
                denomStr = inStr.Split('/')[1];
                // Split by '-' for each unit
                foreach (string s in numStr.Split(' '))
                {
                    // Do not asign units of "1"
                    if (s == "1") break;
                    // If powers, assign them
                    if (s.Contains('^'))
                        unitList.Add(new SingleUnit(s.Split('^')[0], Convert.ToInt32(s.Split('^')[1])));
                    else
                        unitList.Add(new SingleUnit(s));
                }
                // Split by '-' for each unit
                foreach (string s in denomStr.Split(' '))
                {
                    // Do not asign units of "1"
                    if (s == "1") break;
                    // If powers, assign them
                    if (s.Contains('^'))
                        unitList.Add(new SingleUnit(s.Split('^')[0], -1 * Convert.ToInt32(s.Split('^')[1])));
                    else
                        unitList.Add(new SingleUnit(s, -1));
                }
            }
            // Only has a numerator
            else
            {
                // Split by '-' for each unit
                foreach (string s in inStr.Split(' '))
                {
                    // If powers, assign them
                    if (s.Contains('^'))
                        unitList.Add(new SingleUnit(s.Split('^')[0], Convert.ToInt32(s.Split('^')[1])));
                    else
                        unitList.Add(new SingleUnit(s));
                }
            }

            Simplify();
        }

        /// <summary>
        /// Override of GetHashCode.
        /// </summary>
        /// <returns>Returns an int.</returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        # region operators

        /// <summary>
        /// Override of equals.
        /// </summary>
        /// <param name="obj">Object to check equality on.</param>
        /// <returns>Returns true if equal.</returns>
        public override bool Equals(object obj)
        {
            // Check for null
            if (obj == null)
                return false;
            
            // Check for uncastable
            Unit u1 = obj as Unit;
            if (u1 == null)
                return false;
            
            // Check length
            if (unitList.Count != u1.unitList.Count)
                return false;
            
            // Check nums
            for (int i = 0; i < unitList.Count; i++)
                if (unitList[i] != u1.unitList[i]) return false;
            
            return true; 
        }

        /// <summary>
        /// Equality override. 
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>True if equivalent.</returns>
        public static bool operator ==(Unit u1, Unit u2)
        {
            return u1.Equals(u2);
        }

        /// <summary>
        /// Not-equal override.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>True if not equivalent.</returns>
        public static bool operator !=(Unit u1, Unit u2)
        {
            return !(u1 == u2);
        }

        /// <summary>
        /// Addition override.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>Returns a Unit which is the sum of the two inputs.</returns>
        public static Unit operator +(Unit u1, Unit u2)
        {
            if (u1 == u2)
                return u1;
            else
                throw new Exception("You can not add two units of different types.");
        }

        /// <summary>
        /// Subtraction override.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>Returns a Unit whic is the difference of the two inputs.</returns>
        public static Unit operator -(Unit u1, Unit u2)
        {
            if (u1 == u2)
                return u1;
            else
                throw new Exception("You can not subtract two units of different types.");
        }

        /// <summary>
        /// Multiplication override.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>Returns the produt of the two inputs.</returns>
        public static Unit operator *(Unit u1, Unit u2)
        {
            Unit retUnit = new Unit(""); 

            // Combine units
            foreach (SingleUnit single1 in u1.unitList)
                retUnit.unitList.Add(new SingleUnit(single1));
            foreach (SingleUnit single2 in u2.unitList)
                retUnit.unitList.Add(new SingleUnit(single2));

            retUnit.Simplify();

            return retUnit;  
        }

        /// <summary>
        /// Division override.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="u2">Unit.</param>
        /// <returns>Returns the quotient of the two inputs. </returns>
        public static Unit operator /(Unit u1, Unit u2)
        {
            Unit retUnit = new Unit(""); 

            // Combine num1 & denom2
            foreach (SingleUnit single1 in u1.unitList)
                retUnit.unitList.Add(new SingleUnit(single1));
            foreach (SingleUnit single2 in u2.unitList)
                retUnit.unitList.Add(new SingleUnit(single2.BaseVar, -1*single2.Exp));
            
            retUnit.Simplify();

            return retUnit; 
        }

        /// <summary>
        /// Power of units.
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="d">Double.</param>
        /// <returns>Returns a Unit raised to the power of d.</returns>
        public static Unit pow(Unit u1, double d)
        {
            Unit retUnit = new Unit("");

            foreach (SingleUnit single in u1.unitList)
                retUnit.unitList.Add(new SingleUnit(single.BaseVar, single.Exp * d));

            retUnit.Simplify();

            return retUnit;
        }

        /// <summary>
        /// See pow().
        /// </summary>
        /// <param name="u1">Unit.</param>
        /// <param name="d">Double.</param>
        /// <returns>Returns a Unit raised to the power of d.</returns>
        public static Unit operator ^(Unit u1, double d)
        {
            return pow(u1, d);
        }

        /// <summary>
        /// Returns true if the input Unit is contained in this Unit.
        /// </summary>
        /// <param name="u1">Input Unit.</param>
        /// <returns>Boolean true if input is contained in self.</returns>
        public bool Contains(Unit u1)
        {
            // Verify that all the base values exist
            foreach (SingleUnit u in u1.unitList)
                if (this.ContainsBase(u) == false) return false;            

            // Check that the powers of each Unit are valid.
            foreach (SingleUnit u in u1.unitList)
            {
                foreach (SingleUnit x in unitList)
                {
                    if (u.BaseVar == x.BaseVar)
                    {
                        if (x.Exp > 0 && x.Exp > u.Exp)
                            return false;
                        else if (x.Exp < 0 && x.Exp < u.Exp)
                            return false;
                    }
                }
            }
            return true;
        }
        
        private bool ContainsBase(SingleUnit u1)
        {
            foreach (SingleUnit x in unitList)
            {
                if (u1.BaseVar == x.BaseVar) return true;
            }
            return false;
        }

        /// <summary>
        /// Returns true if the unit (input as a string) is contained
        /// in self. 
        /// </summary>
        /// <param name="str">Input string.</param>
        /// <returns>Boolean true if input is contained in self.</returns>
        public bool Contains(string str)
        {
            return Contains(new Unit(str));
        }

        # endregion 


        # region Simplifiers

        private void Simplify()
        {
            bool debug = false;
            if (debug) Console.WriteLine("preExpand: " + this.ToString());
            Expand();
            if (debug) Console.WriteLine("preReduce: " + this.ToString());
            Reduce();
            // Tries equivalence with expanded and collapsed values.
            if (debug) Console.WriteLine("preEquivalence: " + this.ToString());
            Expand();
            Equivalence();
            Reduce();
            //Equivalence();
            //Reduce();


        }

        /// <summary>
        /// Exands all values in the unitList to aid simplification.
        /// </summary>
        private void Expand()
        {
            for (int i = 0; i < unitList.Count; i++)
            {
                if (unitList[i].Exp > 1)
                {
                    unitList[i].Exp--;
                    unitList.Add(new SingleUnit(unitList[i].BaseVar));
                    Expand();
                    return;
                }

                if (unitList[i].Exp < -1)
                {
                    unitList[i].Exp++;
                    unitList.Add(new SingleUnit(unitList[i].BaseVar, -1));
                    Expand();
                    return;
                }
            }

            // #Sort??
        }


        

        /// <summary>
        /// Exchanges equivalent units.
        /// </summary>
        private void Equivalence()
        {
            //XmlDocument xDoc = new XmlDocument();
            //xDoc.Load("EquivalenceList.xml");
            //XmlElement root = xDoc.DocumentElement;

            //foreach (XmlElement x in root.ChildNodes)
            //{
            //    string eq1 = x.Name;
            //    string eq2 = x.InnerText;

            //    //if (this.Contains(eq2)) { }
            //}

            // Something funky here.... >.>


            // # needs work
        }

        /// <summary>
        /// Reduces the units and combines terms.
        /// </summary>
        private void Reduce()
        {
            // Already collapsed
            if (unitList.Count <= 1)
                return;

            // Compare each value in the numerator
            for (int i = 0; i < unitList.Count - 1; i++)
            {
                for (int j = i + 1; j < unitList.Count; j++)
                {
                    // If it is not itself and the bases are equal,
                    // combine them and run again
                    if (unitList[i].BaseVar == unitList[j].BaseVar)
                    {
                        unitList[i].Exp += unitList[j].Exp;
                        unitList.RemoveAt(j);
                        if (unitList[i].Exp == 0) unitList.RemoveAt(i);
                        Reduce();
                        return;
                    }
                }
            }

        }


        # endregion

        /// <summary>
        /// Override of ToString.
        /// </summary>
        /// <returns>Returns a string representation of the Unit.</returns>
        public override string ToString()
        {
            string retString = "";

            if (unitList.Count == 0)
                retString += "1";
            else
            {
                foreach (SingleUnit s in unitList)                
                    if (s.Exp > 0) retString += s + " ";
                if (retString.Length == 0) retString += "1";
                else retString = retString.Substring(0, retString.Length - 1);              

                retString += " / ";
                foreach (SingleUnit s in unitList)
                {
                    if (s.Exp == -1) retString += s.BaseVar + " ";
                    else if (s.Exp < 0) retString += s.BaseVar + "^" + s.Exp.ToString().Substring(1) + " ";
                }
                retString = retString.Substring(0, retString.Length - 1);
                if (retString[retString.Length-1] == '/') 
                    retString = retString.Substring(0, retString.Length - 2);
                
                
            }

            return retString;
        }

        /// <summary>
        /// Writes useful debugging values to the console.
        /// </summary>
        public void debug()
        {

            Console.WriteLine("*******Start debug.*******");
            Console.WriteLine("Unit count: " + unitList.Count);

            foreach (SingleUnit s in unitList)
                Console.WriteLine("Single: " + s);

            Console.WriteLine("******* End debug. *********");
        }

    }

    /// <summary>
    /// Represents a single unit, as opposed to the list of units
    /// collected in the Unit class.
    /// </summary>
    public class SingleUnit
    {
        /// <summary>
        /// The base value of the unit as a string.
        /// </summary>
        public string BaseVar { get; set; }
        /// <summary>
        /// The value of the exponent of the unit as an int.
        /// </summary>
        public double Exp { get; set; } 

        /// <summary>
        /// Constructs the SingleUnit from a string and int representing
        /// the base and exponent respectively.
        /// </summary>
        /// <param name="baseVar">Base value as a string.</param>
        /// <param name="exp">Exponent value as an int.</param>
        public SingleUnit(string baseVar, double exp=1)
        {
            BaseVar = baseVar;
            Exp = exp;
        }

        /// <summary>
        /// Constructor for a SingleUnit given a SingleUnit.
        /// </summary>
        /// <param name="input">Input as a SingleUnit</param>
        public SingleUnit(SingleUnit input)
        {
            BaseVar = input.BaseVar;
            Exp = input.Exp;
        }

        /// <summary>
        /// Display string given by the ToString() override.
        /// </summary>
        /// <returns>Retuns a string representing the SingleUnit.</returns>
        public override string ToString()
        {
            if (Exp == 1)
                return BaseVar;
            else if (Exp != 0)
                return BaseVar + "^" + Exp.ToString();
            else
                return BaseVar;
        }
    }
}
